<?php

/**
 * @copyright Michiel Hakvoort 2010
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD
 * @package mangrove
 * @subpackage core
 * @filesource
 */

/*
 * Copyright (c) 2010 Michiel Hakvoort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

namespace mg;

/**
 * The mgEngineException class acts as an alternative for errors in php. When enabled a mgEngineException will be thrown instead
 * of an error being triggered. The mgEngineException also allows insight into analysis of the code which caused the exception to
 * be thrown be examining the scope in which it has been thrown.
 *
 * @author Michiel Hakvoort
 * @version 1.0
 * @see \mg\Core
 */
class EngineException extends Exception {

    private $context;

    /**
     * Create a new mgEngineException.
     *
     * @access public
     * @param string $message The exception message
     * @param int $code The exception code
     * @param string $file The name of the file in which the error occurred
     * @param int $line The line of the file at which the error occurred
     * @param array $context The variables in the scope at the time the error was triggered
     */
    public function __construct($message, $code, $file, $line, $context) {
        parent :: __construct($message, $code);

        $this->file = $file;
        $this->line = $line;
        $this->context = $context;
    }

    /**
     * Throw a new EngineException. If checks are enabled, suppressed errors will be analyzed.
     *
     * @access public
     * @static
     * @param int $code
     * @param string $message
     * @param string $file
     * @param int $line
     * @param array $context
     */
    public static function errorHandler($code, $message, $file, $line, $context) {
        if(error_reporting()) {
            throw new EngineException($message, $code, $file, $line, $context);
        } else {
            	
        }
    }

    /**
     * Return the lines of code where the error occured and evaluate the variables in the context.
     * The analyzed code will extract all variables from the code and link the variables to the
     * actual value at the time the error occurred.
     *
     * @param int $beforeContext The amount of lines before the context
     * @param int $afterContext The amount of lines before the context
     * @return array The array of analyzed code
     */
    public function getAnalyzedLineCode($beforeContext = 2, $afterContext = 2) {
        $lineCode = $this->getLineCode($beforeContext, $afterContext);

        $analyzedCode = array();

        foreach($lineCode as $line => $code) {
            $analyzedLine = array('');
            $scanCode = '<?php ' . $code . ' ?>';
            $tokens = token_get_all($scanCode);
            for($i = 1; $i < count($tokens) - 1; $i++) {
                $token = $tokens[$i];
                if(is_array($token)) {
                    if($token[0] == T_VARIABLE) {
                        $varName = mb_substr($token[1], 1);
                        if(isset($this->context[$varName])) {
                            $analyzedVar = array();
                            $analyzedVar['var'] = $token[1];
                            $analyzedVar['value'] = $this->context[$varName];
                            $analyzedVar['isset'] = true;
                        } else {
                            $analyzedVar = array();
                            $analyzedVar['var'] = $token[1];
                            $analyzedVar['value'] = null;
                            $analyzedVar['isset'] = false;
                        }

                        $analyzedLine[] = $analyzedVar;
                        $analyzedLine[] = '';
                    } else {
                        $analyzedLine[count($analyzedLine) - 1] .= $token[1];
                    }
                } else {
                    $analyzedLine[count($analyzedLine) - 1] .= $token;
                }
            }
            $analyzedCode[$line] = $analyzedLine;
        }
        return $analyzedCode;
    }

    /**
     * Generate an error report for the error. This error report will consist of an array of lines of analyzed code.
     * The analyzed code expresses itself in such that the type or value of a specific variable will be added in braces
     * after the placement of the variable in the code.
     *
     * @param int $beforeContexts Lines of code to show before the line in which the error occurred
     * @param int $afterContext Lines of code to show after the line in which the error occurred
     * @param bool $onlyErrorLine Evaluate only variables on the line in which the error occurred
     * @return array
     */
    public function getLineCodeReport($beforeContext = 2, $afterContext = 2, $onlyErrorLine = true) {
        $analyzedCode = $this->getAnalyzedLineCode($beforeContext, $afterContext);
        $codeReport = array();
        foreach($analyzedCode as $line => $analyzedLine) {
            $code = '';
            foreach($analyzedLine as $partial) {
                if(is_array($partial)) {
                    $code .= $partial['var'];
                    if($line == $this->line || !$onlyErrorLine) {
                        if($partial['isset']) {
                            if(is_scalar($partial['value'])) {
                                $code .= '('.$partial['value'].')';
                            } elseif(is_object($partial['value'])) {
                                $code .= '(obj:'.get_class($partial['value']).')';
                            } else {
                                $code .= '('.gettype($partial['value']).')';
                            }
                        } else {
                            $code .= '(n/a)';
                        }
                    }
                } else {
                    $code .= $partial;
                }
            }
            $codeReport[$line] = $code;
        }
        return $codeReport;
    }

    /**
     * Return the context in which the EngineException was thrown
     *
     * @access public
     * @return array
     */
    public function getContext() {
        return $this->context;
    }
}


